/* * Copyright 2013-2015 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package miscellaneous; import java.util.HashMap; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; /** * A system of procedurally generating sounds, and some presets thereof. This * isn't actually used anywhere, but it's in the library in case it will be * useful. If it's useful to you, please figure out how to merge it nicely into * the library. * * @author skeggsc */ public class Beeper { private static final HashMap<String, BeepType> lookups = new HashMap<String, BeepType>(16); private static BeepType current = null; private static final Object cursync = new Object(); static { for (BeepType bt : BeepType.values()) { lookups.put(bt.name().toUpperCase(), bt); } } static { Thread t = new Thread("beeper") { @Override public void run() { while (true) { BeepType bt; synchronized (cursync) { while (current == null) { try { cursync.wait(); } catch (InterruptedException ex) { } } bt = current; current = null; } beep(bt); } } }; t.setDaemon(true); t.start(); } /** * Test one of the Beep Types: ALARM, ANNOY, LOW, FASTALARM, SIREN, OOPS, * AIMED, CORRAL. * * The type should be passed as an argument. * * @param args contains the beep type to play. * @throws InterruptedException if the main thread gets interrupted while * waiting for the effect to complete. */ public static void main(String[] args) throws InterruptedException { // ALARM, ANNOY, LOW, FASTALARM, SIREN, OOPS, AIMED, CORRAL; beep(args.length == 0 ? "corral" : args[0]); Thread.sleep(5000); } /** * Start playing the named beep type. * * Possible types: ALARM, ANNOY, LOW, FASTALARM, SIREN, OOPS, AIMED, CORRAL. * * If an invalid type is specified, play OOPS. * * @param name the type of beep to play. */ public static void beep(String name) { BeepType bt = lookups.get(name.toUpperCase()); if (bt == null) { bt = BeepType.OOPS; } synchronized (cursync) { current = bt; cursync.notifyAll(); } } /** * Start playing the specified beep type. * * @param bt the beep type to play. * @see BeepType */ public static void beep(BeepType bt) { System.out.println("Beeping: " + bt); try { AudioFormat format = new AudioFormat(bt.rate(), 8, 1, false, true); SourceDataLine line = AudioSystem.getSourceDataLine(format); try { line.open(format); line.start(); long time = 0; while (true) { byte[] more = new byte[1024]; int i = 0; try { for (; i < more.length; i++) { more[i] = bt.generateOne(time++); } } catch (CompletedException ex) { line.write(more, 0, i); break; } line.write(more, 0, i); } line.drain(); } finally { line.close(); } } catch (LineUnavailableException ex) { ex.printStackTrace(); } } /** * The types of beeps that this generator can generate. * * @author skeggsc */ public static enum BeepType { /** * Repetitive warning beeps. */ ALARM, /** * A repetitive annoying sound. */ ANNOY, /** * A sequence of ascending tones. */ LOW, /** * A repeated fast alarm noise. */ FASTALARM, /** * An approximate siren noise. */ SIREN, /** * A high-pitched beep. */ OOPS, /** * Two short beeps, like a lock-on effect. */ AIMED, /** * A single beep, like a notification. */ CORRAL; private byte generateOne(long l) throws CompletedException { long max; switch (this) { case SIREN: case FASTALARM: case ALARM: case LOW: case ANNOY: max = 32000; break; case CORRAL: max = 1600; break; case OOPS: max = 4000; break; case AIMED: max = 3000; break; default: throw new IllegalStateException(); } if (l > max) { throw new CompletedException(); } switch (this) { case AIMED: return (byte) ((l * ((l % 1500 < 600) ? 480 : 0)) & 0xFF); case OOPS: return (byte) (l * 64); case SIREN: return (byte) ((l % 6400) * (12 + (l / 800.0) % 8)); case FASTALARM: return (byte) ((l % 800 >= 400) ? 0 : l * 66); case CORRAL: return (byte) ((l % 1600 >= 800) ? 0 : l * 57); case ALARM: return (byte) ((l >= 800) ? 0 : l * 57); case LOW: return (byte) ((l % 2400 >= 1200) ? 0 : l * (6 + ((l / 2400) % 5))); case ANNOY: return (byte) ((l % 400 >= 200) ? 0 : (l % 1200 >= 800) ? l << 7 : (l % 1200 >= 400) ? l << 4 : l << 3); default: throw new IllegalStateException(); } } private float rate() { return this == AIMED ? 16000f : 8000f; } } @SuppressWarnings(value = "serial") private static final class CompletedException extends Exception { } }